﻿#region License
// Copyright © 2004 - 2010 All Right Reserved, Derek Goslin
// 
// 1. Definitions
// The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under U.S. copyright law.
// A "contribution" is the original software, or any additions or changes to the software.
// A "contributor" is any person that distributes its contribution under this license.
// "Licensed patents" are a contributor's patent claims that read directly on its contribution.
// 
// 2. Grant of Rights
// (A) Copyright Grant- Subject to the terms of this license, including the license
//     conditions and limitations in section 3, each contributor grants you a non-exclusive,
//     worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative 
//     works of its contribution, and distribute its contribution or any derivative works that you create.
// (B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations
//     in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free license under its
//     licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of
//     its contribution in the software or derivative works of the contribution in the software.
// 
// 3. Conditions and Limitations
// (A) Reciprocal Grants- For any file you distribute that contains code from the software (in source code or binary format), 
//     you must provide recipients the source code to that file along with a copy of this license, which license will govern that file. 
//     You may license other files that are entirely your own work and do not contain code from the software under any terms you choose.
// (B) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks.
// (C) If you bring a patent claim against any contributor over patents that you claim are infringed by the software,
//     your patent license from such contributor to the software ends automatically.
// (D) If you distribute any portion of the software, you must retain all copyright, patent, trademark,
//     and attribution notices that are present in the software.
// (E) If you distribute any portion of the software in source code form, you may do so only under this license by including a complete copy 
//     of this license with your distribution. If you distribute any portion of the software in compiled or object code form, you may only do 
//     so under a license that complies with this license.
// (F) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees or conditions.
//    You may have additional consumer rights under your local laws which this license cannot change. To the extent permitted under your
//    local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular purpose and non-infringement.
#endregion

using System;
using System.Collections;
using System.ComponentModel;
using System.Reflection;

namespace DnEcuDiag.DataBinding
{
    public class SortableSearchableList<T> : BindingList<T>
    {
        private ListSortDirection _sortDirectionValue;
        private ArrayList _sortedList;
        private PropertyDescriptor _sortPropertyValue;
        private ArrayList _unsortedItems;

        protected override PropertyDescriptor SortPropertyCore
        {
            get { return _sortPropertyValue; }
        }

        protected override ListSortDirection SortDirectionCore
        {
            get { return _sortDirectionValue; }
        }

        protected override bool SupportsSortingCore
        {
            get { return true; }
        }

        protected override bool SupportsSearchingCore
        {
            get { return true; }
        }

        public override void EndNew(int itemIndex)
        {
            if (_sortPropertyValue != null && itemIndex > 0 && itemIndex == Count - 1)
            {
                ApplySortCore(_sortPropertyValue, _sortDirectionValue);
            }
            base.EndNew(itemIndex);
        }

        public void RemoveSort()
        {
            RemoveSortCore();
        }

        protected override void ApplySortCore(PropertyDescriptor prop,
                                              ListSortDirection direction)
        {
            _sortedList = new ArrayList();

            // Check to see if the property type we are sorting by implements
            // the IComparable interface.
            Type interfaceType = prop.PropertyType.GetInterface("IComparable");

            if (interfaceType != null)
            {
                // If so, set the SortPropertyValue and SortDirectionValue.
                _sortPropertyValue = prop;
                _sortDirectionValue = direction;

                _unsortedItems = new ArrayList(Count);

                // Loop through each item, adding it the the sortedItems ArrayList.
                foreach (Object item in Items)
                {
                    _sortedList.Add(prop.GetValue(item));
                    _unsortedItems.Add(item);
                }

                // Call Sort on the ArrayList.
                _sortedList.Sort();

                // Check the sort direction and then copy the sorted items
                // back into the list.
                if (direction == ListSortDirection.Descending)
                {
                    _sortedList.Reverse();
                }

                for (int i = 0; i < Count; i++)
                {
                    int position = Find(prop.Name, _sortedList[i]);
                    if (position != i)
                    {
                        T temp = this[i];
                        this[i] = this[position];
                        this[position] = temp;
                    }
                }
                // Raise the ListChanged event so bound controls refresh their
                // values.
                OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
            }
            else
            {
                // If the property type does not implement IComparable, let the user
                // know.
                throw new NotSupportedException("Cannot sort by " + prop.Name + ". This" + prop.PropertyType + " does not implement IComparable");    
            }
        }

        protected override void RemoveSortCore()
        {
            // Ensure the list has been sorted.
            if (_unsortedItems != null)
            {
                // Loop through the unsorted items and reorder the
                // list per the unsorted list.
                for (int i = 0; i < _unsortedItems.Count;)
                {
                    int position = Find(SortPropertyCore.Name,
                                        _unsortedItems[i].GetType().
                                            GetProperty(SortPropertyCore.Name).
                                            GetValue(_unsortedItems[i], null));
                    if (position >= 0 && position != i)
                    {
                        object temp = this[i];
                        this[i] = this[position];
                        this[position] = (T) temp;
                        i++;
                    }
                    else if (position == i)
                    {
                        i++;
                    }
                    else
                    {
                        // If an item in the unsorted list no longer exists, delete it.
                        _unsortedItems.RemoveAt(i);
                    }
                }
                OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
            }
        }

        protected override int FindCore(PropertyDescriptor prop, object key)
        {
            // Get the property info for the specified property.
            PropertyInfo propInfo = typeof (T).GetProperty(prop.Name);

            if (key != null)
            {
                // Loop through the the items to see if the key
                // value matches the property value.
                for (int i = 0; i < Count; ++i)
                {
                    T item = Items[i];
                    if (propInfo.GetValue(item, null).Equals(key))
                    {
                        return i;
                    }
                }
            }
            return -1;
        }

        public int Find(string property, object key)
        {
            // Check the properties for a property with the specified name.
            PropertyDescriptorCollection properties =
                TypeDescriptor.GetProperties(typeof (T));
            PropertyDescriptor prop = properties.Find(property, true);

            // If there is not a match, return -1 otherwise pass search to
            // FindCore method.
            if (prop == null)
            {
                return -1;
            }
            
            return FindCore(prop, key);
        }
    }
}